#!/usr/bin/env python
# -*- coding: UTF-8 -*-

#  NMEAReceiver based on NMEA 0183 implementation from U{Bob Ippolito<mailto:bob@redivi.com>}

import serial
import time
import datetime
import operator
import subprocess
import io
from optparse import OptionParser

PORT='/dev/ttyUSB0'
BAUDRATE =9600
PARITY=serial.PARITY_EVEN
TIMEOUT=2.0
RTSCTS=1

def safe_float(s):
    try:
        f = float(s)
    except ValueError:
        f = None        
    return f
    
class gpsfix(object):
    def __init__(self):
        self.status = False
        self.utc = None
        self.latitude = None
        self.longitude = None
        self.quality=0
        self.satellites=None
        self.altitude=None
        self.altitude_unit=None
        self.hdop=None
        self.geoid=None
        self.geoid_unit=None
        self.age=None
        self.refid=None
        

    def __repr__(self):
        if not self.status:
            rep = "No Fix"+'\n'
        else:
            rep = "Fix valid"
            rep +="  Lat/Lon:            %s %s\n"%(self.latitude,self.longitude)
        return rep

    def set_status(self,s):
        if s == 'A':
            self.status=True
            self.valid=True
        elif s == 'V':
            self.status=True
            self.valid=False
        else:
            self.status=False
            self.valid=False
            
    def set_latlon(self, latitude, ns, longitude, ew):
        latitude = float(latitude[:2]) + float(latitude[2:])/60.0
        if ns == 'S':
            latitude = -latitude
        longitude = float(longitude[:3]) + float(longitude[3:])/60.0
        if ew == 'W':
            longitude = -longitude
        self.latitude= latitude
        self.longitude = longitude

            
class gpsposition(gpsfix):
    def __init__(self):
        self.status = False
        self.valid = False
        self.latitude = None
        self.longitude = None
        self.utc = None
        self.speed = None
        self.course = None
        self.magvar = None
        
    def __repr__(self):
        if not self.status:
            rep = ""
        else:
            rep ="Position %s:\n"%(self.get_valid_str())
            rep +="  Date/Time:  %s %s\n"%(self.get_date(),self.get_time())
            rep +="  Lat/Lon:    %s %s"%(self.latitude,self.longitude)
        return rep
        
    def get_valid_str(self):
        if self.valid:
            return "valid"
        else:
            return "invalid"        
    def get_date(self):
        return self.utc.strftime('%d.%m.%Y')
        
    def get_time(self):
        return self.utc.strftime('%H:%M:%S')
        
    def get_datetime(self):
        return self.utc
        
    def get_epoch(self):
        return time.mktime(self.utc.timetuple())
        
     
    def set_utc(self,utcdate,utctime):
        self.utc=datetime.datetime(2000+int(utcdate[4:6]), int(utcdate[2:4]), int(utcdate[0:2]),\
                                   int(utctime[:2]), int(utctime[2:4]), int(utctime[4:6]))
                                   
    def set_speed(self,speed):
        self.speed = safe_float(speed)
        
    def set_course(self,course):
        self.course = safe_float(course)
        
    def set_magvar(self,magvar,magdir):
        if magdir == 'E':
            self.magvar = safe_float(magvar)
        elif magdir == 'W':
            self.magvar = -safe_float(magvar)
        else:
            self.magvar = None

class NMEAReceiver(object):
    """This parses most common NMEA-0183 messages, presumably from a serial GPS device at 4800 bps
    """
    def __init__(self,port=PORT,baudrate=BAUDRATE,parity=PARITY,timeout=TIMEOUT, rtscts=RTSCTS,debug=False):
        
        self.port=port
        self.baudrate=baudrate
        self.parity=parity
        self.timeout=float(timeout)
        self.rtscts=rtscts
        self.debug = debug

        self._connect()
        
        self.dispatch = {
            'GPGGA': 'fix',
            #'GPGLL': 'position',
            #'GPGSA': 'activesatellites',
            'GPRMC': 'positiontime',
            #'GPGSV': 'viewsatellites',    # not implemented
            #'GPVTG': 'course',            # not implemented
            #'GPALM': 'almanac',           # not implemented
            #'GPGRS': 'range',             # not implemented
            #'GPGST': 'noise',             # not implemented
            #'GPMSS': 'beacon',            # not implemented
            #'GPZDA': 'time',              # not implemented
        }
        
        self.fix = gpsfix()
        self.pos = gpsposition()
        
    def _connect(self):
        if self.debug:
            print "Connecting to gps on port",self.port,
        self.tty=False
        try:
            self.tty = serial.Serial(port=self.port, baudrate=self.baudrate, timeout=self.timeout,\
                                     parity=self.parity, rtscts=self.rtscts)
            if self.debug:
                print "OK"
        except serial.serialutil.SerialException:
            if self.debug:
                print "failed"
    
    def _open(self):
        if not self.tty.isOpen():
            self.tty.open()     
               
    def _close(self):
        if self.tty.isOpen():
            self.tty.close()
        
        
    def __repr__(self):
        st = self.pos.__repr__()
        return st
        
    def update(self,fh=None,update=1.0):

        self.pos.status=False
        self.pos.valid=False 

        try:
            self._open()
            time.sleep(float(update))
            while self.tty.inWaiting() > 1:
                rl = self.tty.readline()
                self.decode_nmea(rl)
                if fh:
                    fh.write(rl)
            self._close()
        except:
            time.sleep(float(update))
            self._connect()
            
        
    def checksum_nmea(self, line):
        """
        Check a string to see if it is a valid NMEA 0183 sentence
        """
        if len(line) <4:
            return False
        if line[-2:]=='\r\n':
            line = line[:-2]
        if line[0] != '$':
            return False
        if line[-3] != '*':
            return False

        checksum = 0
        for c in line[1:-3]:
            checksum ^= ord(c)

        if int(line[-2:],16) != checksum:
            return False

        return True
    def split_nmea(self, line):
        if line[-2:]=='\r\n':
            line = line[:-2]
            
        line,checksum = line[:-3],line[-3:]
        
        line = line.split(',')
        sentencetype, message =  line[0],line[1:]
        if sentencetype[0]=='$':
            sentencetype = sentencetype[1:]
        return sentencetype, message, checksum
        
    def decode_nmea(self, line):
        if not self.checksum_nmea(line):
            return False
       
        sentencetype, message, checksum = self.split_nmea(line)
        dispatch = self.dispatch.get(sentencetype, None)
        if self.debug:
            print sentencetype, message
                
        decoder = getattr(self, "decode_%s" % dispatch, None)
        
        if decoder:
            decoder(*message)
            return True
        else:
            return False
    def decode_positiontime(self, utctime, status, latitude, ns, longitude, ew, speed, course, utcdate, magvar, magdir,unk=None):
        self.pos.set_status(status)
        self.pos.set_utc(utcdate, utctime)
        self.pos.set_latlon(latitude, ns, longitude, ew)
        self.pos.set_speed(speed*1.852)
        self.pos.set_course(course)
        self.pos.set_magvar(magvar,magdir)
        
    def decode_activesatellites(self, mode1, mode2, *args):
        self.satellites, (self.pdop, self.hdop, self.vdop) = args[:12], map(safe_float, args[12:])
        satlist = []
        self.mode = (mode1, int(mode2))
        
    def decode_fix(self, utc, latitude, ns, longitude, ew, posfix, satellites, hdop, altitude, altitude_units, geoid_separation, geoid_separation_units, dgps_age, dgps_station_id):
        self.fix.status = True
        self.fix.latitude, self.fix.longitude = self._decode_latlon(latitude, ns, longitude, ew)
        self.fix.time = datetime.time(int(utc[:2]), int(utc[2:4]), float(utc[4:]))

        self.fix.mode = int(posfix)
        self.satellites_used = int(satellites)
        self.hdop_fix = safe_float(hdop)
        self.altitude_fix = (safe_float(altitude), altitude_units)
        if geoid_separation != '':
            self.geoid = (float(geoid_separation), geoid_separation_units)
        else:
            self.geoid = None
        if dgps_age != '':
            self.dgps = (float(dgps_age), dgps_station_id)
        else:
            self.dgps = None
            
    def get_datetime(self):
        return self.pos.get_datetime()
        
    def get_latitude(self):
        return self.pos.latitude
        
    def get_longitude(self):
        return self.pos.longitude
        
    def get_status(self):
        return self.pos.status
        
def main():
    parser = OptionParser()
    parser.add_option("-p", "--port", dest="port", default=PORT, metavar="PORT",
                                      help="read from port [%default]")
    parser.add_option("-b", "--baudrate", dest="baudrate", default=BAUDRATE, metavar="BAUDRATE",
                                      help="use baudrate [%default]")
    parser.add_option("-o","--filename-out", dest="filename_out", default='', metavar="file",
                                      help="filename for data logging")
    parser.add_option("-B","--buffering", dest="buffering", default=0, metavar="BUFFERING",
                                      help="buffer file I/O [%default]")
    parser.add_option("-c","--connect", dest="connect", default=False, action="store_true",
                                      help="connect to gps")
    parser.add_option("-d", "--debug",
              action="store_true", dest="debug", default=False,
              help="enable debug")
    parser.add_option("-v", "--verbose",
                  action="store_true", dest="verbose", default=False,
                  help="be verbose")

    (options, args) = parser.parse_args()

    gps = NMEAReceiver(port=options.port,baudrate=options.baudrate,debug=options.debug)
    
    if options.filename_out:
        fh =  io.open(options.filename_out,'wb',buffering=int(options.buffering))
    else:
        fh = None
        
    if not options.connect:
        parser.print_help()
        exit(-1)
    
    while options.connect:
        gps.update(fh)
        if options.verbose:
            print gps    

if __name__ == "__main__":
    main()

        
